package {


public final class Meson {
  import flash.utils.Dictionary;
  import flash.utils.ByteArray;

  public static var on_message:Function = null;
  public static var nodes:Dictionary = new Dictionary();

  public static function addrToIdent(ip:String, port:int):String {
    return port.toString() + '@' + ip;
  }
  
  public function identToAddr(ident:String):Array {
    var parts:Array = ident.split('@');
    return new Array(parts[1], new int(parts[0]));
  }
  

  public function connect(ident:String):Channel {
    var ch:Channel = nodes[ident];
    if (ch) {
      return ch;
    }
    var addr:Array = identToAddr(ident);
    ch = new Channel(addr[0], addr[1])
    return ch;
  }


  public function init(hook:Function):void {
    on_message = hook;
  }


  public function send(msg:ByteArray, ident:String):void {
    var ch:Channel = connect(ident);
    if (ch) {
      ch.push(msg);
    }
  }

}

}


class Channel {
  import flash.net.Socket;
  import flash.utils.Endian;
  import flash.utils.ByteArray;
  import flash.events.Event;
  import flash.events.ProgressEvent;
  import flash.events.IOErrorEvent;

  public var sock:Socket;
  public var buffered:ByteArray;
  public var peer:String;
  public var stage:int;
  public var pending:int;


  public function Channel(host:String, port:int) {
    sock = new Socket(host, port);
    sock.endian = Endian.LITTLE_ENDIAN;

    sock.addEventListener(Event.CONNECT, onConnect);
    sock.addEventListener(ProgressEvent.SOCKET_DATA, onData);
    sock.addEventListener(Event.CLOSE, onClose);
    sock.addEventListener(IOErrorEvent.IO_ERROR, onError);

    peer = Meson.addrToIdent(host, port);
    Meson.nodes[peer] = this;
    stage = 1;
    pending = 2;
  }

  public function push(data:ByteArray):void {
    if (sock.connected) {
      sock.writeShort(data.length);
      sock.writeBytes(data);
      sock.flush();
    } else {
      if (!buffered) {
        buffered = new ByteArray;
      }
      buffered.writeShort(data.length);
      buffered.writeBytes(data);
    }
  }

  public function onConnect(evt:Event):void {
    sock.writeShort(0);
    if (buffered) {
      sock.writeBytes(buffered);
      buffered = null;
    }
    sock.flush();
  }

  public function onData(evt:ProgressEvent):void {
    while (sock.bytesAvailable >= pending) {
      if (stage == 1) {
        stage = 2;
        pending = new uint(sock.readShort());
      } else {
        stage = 1;
        var buf:ByteArray = new ByteArray;
        Meson.on_message(buf, peer);
      }
    }
  }

  public function onClose(evt:Event):void {
    delete Meson.nodes[peer];
  }

  public function onError(evt:IOErrorEvent):void {
    onClose(evt);
  }
}
